#pragma once

#include <cstddef>
#include <new>
#include <stdexcept>
#include <cassert>


//#define DEBUG_ALLOCATOR

///////////////////////////////////////////////////////////////////

#ifdef DEBUG_ALLOCATOR
#include <iostream>
#include <typeinfo>
#endif

#ifdef DEBUG_ALLOCATOR
#define LOG_CLASS_NAME(msg, obj)	std::clog << (msg) << typeid(obj).name() << '\n'
#else
#define LOG_CLASS_NAME(msg, obj)	(void)0
#endif

///////////////////////////////////////////////////////////////////
/**
 *  @attention
 *  allocate memory by FIFO strategy.
 *  don't miss destroy the allocated objects exactly in @b reverse order of the order of creation.
 */
class IStackAllocator
{
public:
	virtual ~IStackAllocator() {}

public:
	virtual void* allocate(std::size_t n) = 0; //if failed, throw std::bad_alloc
	virtual void deallocate(void* p) = 0;
	virtual std::size_t size() const = 0;

public:
	//construction helpers

	/** @todo use boost::param_traits */
	template<typename T, typename ParamT1>
	T* create(const ParamT1& param1)
	{
		void* p = allocate(sizeof (T));
		T* obj = new (p) T(param1);
		LOG_CLASS_NAME("created: ", obj);
		return obj;
	}

	template<typename T, typename ParamT1, typename ParamT2>
	T* create(const ParamT1& param1, const ParamT2& param2)
	{
		void* p = allocate(sizeof (T));
		T* obj = new (p) T(param1, param2);
		LOG_CLASS_NAME("created: ", obj);
		return obj;
	}

	template<typename T, typename ParamT1, typename ParamT2, typename ParamT3>
	T* create(const ParamT1& param1, const ParamT2& param2, const ParamT3& param3)
	{
		void* p = allocate(sizeof (T));
		T* obj =  new (p) T(param1, param2, param3);
		LOG_CLASS_NAME("created: ", obj);
		return obj;
	}

	template<typename T, typename ParamT1, typename ParamT2, typename ParamT3, typename ParamT4>
	T* create(const ParamT1& param1, const ParamT2& param2, const ParamT3& param3, const ParamT4& param4)
	{
		void* p = allocate(sizeof (T));
		T* obj =  new (p) T(param1, param2, param3, param4);
		LOG_CLASS_NAME("created: ", obj);
		return obj;
	}

	template<typename T, typename ParamT1, typename ParamT2, typename ParamT3, typename ParamT4, typename ParamT5>
	T* create(const ParamT1& param1, const ParamT2& param2, const ParamT3& param3, const ParamT4& param4, const ParamT5& param5)
	{
		void* p = allocate(sizeof (T));
		T* obj =  new (p) T(param1, param2, param3, param4, param5);
		LOG_CLASS_NAME("created: ", obj);
		return obj;
	}

	template<typename T, typename ParamT1, typename ParamT2, typename ParamT3, typename ParamT4, typename ParamT5, typename ParamT6>
	T* create(const ParamT1& param1, const ParamT2& param2, const ParamT3& param3, const ParamT4& param4, const ParamT5& param5, const ParamT6& param6)
	{
		void* p = allocate(sizeof (T));
		T* obj =  new (p) T(param1, param2, param3, param4, param5, param6);
		LOG_CLASS_NAME("created: ", obj);
		return obj;
	}
	template<typename T>
	void destroy(T*& p)
	{
		if (p)
		{
			LOG_CLASS_NAME("destroy: ", p);
			p->~T();
			deallocate(p);
			p = NULL;
		}
		assert(!p);
	}

};

///////////////////////////////////////////////////////////////////

class StackAllocator : public IStackAllocator
{
public:
	explicit StackAllocator(std::size_t n);

	~StackAllocator();

	virtual void* allocate(std::size_t n);

	virtual void deallocate(void* p);

	virtual std::size_t size() const
	{
		return (current_ - begin_);
	}

	std::size_t capacity() const
	{
		return (end_ - begin_);
	}

	std::size_t freeSize() const
	{
		return (end_ - current_);
	}
private:
	StackAllocator(const StackAllocator&);
	StackAllocator& operator=(const StackAllocator&);

private:
	static const std::size_t sizeOfSize = sizeof (std::size_t);
	unsigned char* begin_;
	unsigned char* end_;
	unsigned char* current_;
#ifdef DEBUG_ALLOCATOR
	std::size_t maxAllocatedSize_;
#endif
};


///////////////////////////////////////////////////////////////////

/** allocate by new[]/delete[]
 *  @note this inheritance  meets Liskov Substitution Principal.
 *        interface of stack is more limited.
 */
class HeapAllocator : public IStackAllocator
{
public:
	virtual void* allocate(std::size_t n)
	{
		return new unsigned char[n];
	}
	virtual void deallocate(void* p)
	{
		delete [] p;
	}


	/** @attention dummy! */
	virtual std::size_t size() const
	{
		return 0;
	}
	static HeapAllocator theInstance;

private:
	HeapAllocator();
	HeapAllocator(const HeapAllocator&);
};

///////////////////////////////////////////////////////////////////

#include <cassert>

class StackValidator
{
public:
	explicit StackValidator(const IStackAllocator& stack)
		: oldSize_(stack.size()), stack_(&stack)
	{
	}

	~StackValidator()
	{
		assert(oldSize_ == stack_->size());
	}

private:
	std::size_t oldSize_;
	const IStackAllocator* stack_;
};

#ifndef NDEBUG
#define VALIDATE_STACK(stack)	StackValidator stackValidator_((stack))
#else
#define VALIDATE_STACK(stack)	(void)0
#endif

